#include "StdAfx.h"
#include "Globals.h"
#include "Draw.h"
#include "MainFunctions.h"
#include "GeneticAlgorithm.h"

using namespace PTT;



//-----------------------------------------------------------------------------
//	size and alpha for the bubbles (onpu)
	float Alpha1(float t)		{	return sqr(sqr(t-1.0f));							}
	float Size1	(float t)		{	return 1.5f * (1.0f - sqr(t-1.0f));				}

//-----------------------------------------------------------------------------
//	size and alpha for the sun corona flashes
	float Alpha2(float t)		{	return 0.5f*sqr(sqr(t-1.2f));						}
	float Size2	(float t)		{	return 8.0f*sqr(t-0.8f);							}

//-----------------------------------------------------------------------------
//	size and alpha for "combo" and "chance time" display
	float Alpha3(float t)		{	return 1.0f - sqr(sqr(sqr(t)));					}
	float Size3 (float t)		{	return max(1.0f, 1.5f-sqr(sqr(sqr(4.0f*(t-0.2f)))));}



//-----------------------------------------------------------------------------
// score index corresponds to school grades [1=best, ..., 5=worst]
static const	std::wstring	ratingTable[6]		= {L"", L"COOL", L"FINE", L"SAFE", L"SAD", L"WORST"};
static const	int				scoreTable [6]		= {0, 500, 300, 100, 50, 0};
static const	Color				colorTable [6]		= {white, Color(255,200,0), Color(144,202,234), Color(45,200,104), Color(50,128,210), Color(200,45,235)};
static const	int				ratingKerning[6]	= {0, 196, 156, 192, 154, 262};

//-----------------------------------------------------------------------------
//	Variables that are "shared" between the two threads.
//	They are almost exclusively written by the input thread and
//	read by the main thread. Except for initilization.
//	So no synchronization is necessary.
//-----------------------------------------------------------------------------
//	For smooth animations, we cannot rely on the audiostream alone for timing, as it has a resolution of 10ms.
//	We use our own timer, and just adjust the frametimes slightly if we go async to the audio, which shouldn't happen very often, though.

//-----------------------------------------------------------------------------
DWORD					threadsSleep		= 20;					// number of milliseconds, the input and video threads sleep after each step.
bool					exitThreads			= false;				// request input and video threads to return
bool					InputThreadExited	= false;				// has the input thread returned?
bool					VideoThreadExited	= false;				// has the video thread returned?
bool					updateIsSafe		= false;				// if true, it's safe to call InputUpdate, as all variables are initialized.
CRITICAL_SECTION	updateIsSafeAccess;						// read and write access to "updateIsSafe"
bool					StopSong				= false;				// if true, the input thread will fade out and stop the main song.
bool					DoVideoDecoding	= false;				// if true, the video thread will decode the video from 'vFile'
bool					StartOptimization	= false;				// Will optimize the paths, until 'StopOptimizing' gets set, just before the first button must be hit.
float					aspectRatio			= 1.33333333f;		// For velocity correction after optimization

//-----------------------------------------------------------------------------
//	These are initialized by the main thread, for each new song.
Counter				timer;										// high performance counter, for accurate timing
MediaFile			*aFile	= NULL;							// audio file
MediaFile			*vFile	= NULL;							// video file; can be the same as aFile
bool					aStreamStarted;							// will be set to true, the first time aFile->play() is called

int					hitIndex;									// number of finished hit objects
bool					paused;										// is the game paused
int					HUD_mode			= 0;						// {0 = overlayed, 1 = transparent, 2 = outside}
bool					autoPlay			= false;					// game will automatically hit the correct notes
bool					cheated;										// game won't save highscore when auto play was enabled
bool					hideScore		= false;					// game won't draw scoreboard, after finished
int					visualMode		= 0;						// {0 = none, 1 = waveform, 2 = spectrum, 3 = spectrum dbfs}
int					visualSamples	= 512;					// Number of samples visualized per frame - at 44100 Hz and 60 fps, 735 would be played per frame, but they're windowed so the edges have less influence.
float					visualHue		= 0.11f;					// Hue of LAB color for visualisation
int					visualITF		= TRUE;					// Do an intrinsic time-scale decomposition instead of a fast fourier transform

bool					correctKey;									// did the player hit the correct button, the last time? Corresponds to "rating".
int					rating;										// last rating index [0,6)
int					score;										// current total score
int					combo;										// current combo
int					maxCombo;									// max combo
float					drawVol;										// time to display volume
int					chanceTimeStart;							// time in milliseconds, when chance time starts
int					chanceTimeStop;							// time in milliseconds, when chance time is over
int					chanceTimeCombo;							// current chance time combo
int					chanceTimeScore;							// total chance time score
int					ratingCount[6];							// total number of different ratings
bool					inputOver;									// no more hit-objects left, but maybe music
bool					exitPlaySong;								// User wants to return to the main menu
double				volume;										// master volume [0,100], not clamped

XINPUT_VIBRATION	vibration;									// The vibration information to send to the controller

std::vector<FlyingButton>	buttons;							// All hit-objects sorted by time
std::vector<PopInTexture>	popIn0,
									popIn1;
PopInTexture					comboTexPopIn;
PopInNumber						comboNumPopIn;
PopInTexture					chanceTimeInfoPopIn;
PopInNumber						chanceTimeResultPop;
PopInNumber						chanceTimePop;

//-----------------------------------------------------------------------------
//	One input update step. Called about every millisecond from the input thread.
//	Could alternatively be called every frame (~17ms) from the main thread.
//	Assumes updateIsSafe==true, which means all above variables are initialized.
//-----------------------------------------------------------------------------

void InputUpdate()
{
//----
//	This timer is only for input scaling
	static Counter t2;
	double t = t2.getAndReset();

//----
//	Input
	Input::UpdateXInput();
	Input::KeyboardState key;
	Input::MouseState		mouse;
	Input::PadState		pad;
	Input::GetAndReset(&key, &mouse, &pad);

//----
//	volume adjustment with mousewheel and left thumbstick
	if(mouse.ScrollUp)	volume+=5;
	if(mouse.ScrollDown)	volume-=5;
	volume += 100.0*t*pad.LThumb.y;
	volume = min(max(volume,0.0),100.0);

//----
	if(pad.D_Left || key.Divide)					visualSamples = max(visualSamples/2, 512);
	if(pad.D_Right|| key.Multiply)				visualSamples = min(visualSamples*2, 16*1024);
	if(pad.D_Up   || key.LastKey==VK_NEXT)		visualMode++;
	if(pad.D_Down || key.LastKey==VK_PRIOR)	visualMode--;
	if(visualMode > 3)
		visualMode = 0;
	if(visualMode < 0)
		visualMode = 3;
	visualHue += float(t*pad.RThumb.x);
	if(key.Add)			visualHue += 0.05f;
	if(key.Subtract)	visualHue -= 0.05f;
	if(visualHue >  1.0f)
		visualHue -= 2.0f;
	if(visualHue < -1.0f)
		visualHue += 2.0f;

//----
//	Start and Escape toggle paused state, or return to main menu
	if(key.Escape||pad.Start)
	{
		if(inputOver)
			exitPlaySong = true;				// exit, but keep music playing
		else
		{
			paused = !paused;
			if(paused)
				timer.stop();
			else
				timer.resume();
		}
	}


//----
//	Read timer and get key hit input
	int keyHit = -1;							// which key was pressed? {-1=none, 0=circle, 1=cross, 2=quad, 3=triangle}
	if(!paused)
	{
	//----
	//	Get pressed key and play sound
		if(pad.B||key.LastKey==VK_BUTTONS[0])	keyHit = 0;
		if(pad.A||key.LastKey==VK_BUTTONS[1])	keyHit = 1;
		if(pad.X||key.LastKey==VK_BUTTONS[2])	keyHit = 2;
		if(pad.Y||key.LastKey==VK_BUTTONS[3])	keyHit = 3;
		if(keyHit>=0)
		{
			drum[keyHit]->play();
			autoPlay = false;
		}
		timer.stop();
	}

//----
// The play time from the audiostream is only updated at a resolution of 10ms,
//	so the time is only accurate, when it just changed -> check these points for synchronity
//	All local (not shared) state variables, must obviously be static.
	static double	lastTimeAudio	= 0;
	static double	divergence		= 0;						// difference between our timer and audio
	static double	counterAdjust	= 0;						// every step (>=10ms), the counter is adjusted by this number of seconds = 2% of audio divergence.

//----
//	Counter to audio adjustment
	double timeAudio	= aFile->playedTime() - 0.001*PTT::ms_error_mode;	// song play time in seconds from audiostream (resolution 10ms)
	double timeS		= timer.result();												// current song play time in seconds from our counter (resolution <1ms), can be negative in the beginning

//----
//	check for start of audio
	if(!paused && timeS >= 0 && aFile->getState()==BufferedVoice::stopped)
	{
		aFile->play();
		lastTimeAudio	= 0;
		divergence		= 0;
		counterAdjust	= 0;
	}

//----
//	pause or resume audio, or exit
	if(key.Escape||pad.Start && inputOver==false)
	{
		if(paused)
		{
			drum[0]->pause();
			drum[1]->pause();
			drum[2]->pause();
			drum[3]->pause();
			aFile->pause();
		}
		else if(timeS >= 0)
		{
			drum[0]->resume();
			drum[1]->resume();
			drum[2]->resume();
			drum[3]->resume();
			aFile->resume();
		}
	}

//----
//	audio timing synchronisation
	if(lastTimeAudio != timeAudio && timeS > 0.0 && !aFile->endOfFile())
	{
		lastTimeAudio	= timeAudio;

		double	oldDiv		= divergence;
					divergence	= timeAudio - timeS;			// difference between what you hear and what you see

		if(abs(divergence) > 0.004)							// start correction if error becomes greater than 4ms
			counterAdjust = 0.1*divergence;					// -> it will take a few steps for adjustment to complete
		else if(abs(divergence) < 0.0004)					// stop correction when divergence is < 1 ms.
			counterAdjust = 0;
		if(counterAdjust)
		{
			timeS = timer.set(timeS + counterAdjust);		// adjust timer
//			std::cout << "audio divergence = " << 0.01*round(100000.0*divergence) << " ms." << std::endl;
		}
	}

	timeAudio = timeS + divergence;

//----
	inputOver |= buttons.size()==0 || (buttons[buttons.size()-1].time < timeS-2.0);

	if(inputOver && (pad.R||key.LastKey==VK_END))
		hideScore = !hideScore;

	if(key.LastKey == 'i')
		visualITF = !visualITF;

	if(key.LastKey == VK_INSERT && key.LastExt)
		autoPlay = !autoPlay;

	if(pad.L || (key.LastKey==VK_DELETE && key.LastExt))
		if(++HUD_mode > 2)
			HUD_mode = 0;

	if(paused && (key.Back||pad.Back))
		exitPlaySong = true;										// exit

//----
//	Handle hit or timeout events.
//	If a key is hit, remove all correct buttons as long as they are late or "cool".
//	This makes it easier to catch up in a fast sequence where you actually hit the right beat, but missed the start which would result in only misses otherwise.
	double minError = 1.0e9;
	for(; hitIndex < (int)buttons.size(); hitIndex++)
	{
		const FlyingButton &h = buttons[hitIndex];
		double	hitMS	= 1000.0*h.time,
					ms		= 1000.0*(timeS - h.time);			// time relative to the next event in milliseconds

	//----
	//	AUTO-PLAY
		if(autoPlay &&  ms >= 0)
		{
			cheated = true;
			keyHit = buttons[hitIndex].id;
			drum[keyHit]->play();
		}

		bool hit			= (ms > -250.0 && keyHit >= 0);
		bool timeout	= (ms >= 250.0);

		if(!hit && !timeout)
			break;

		correctKey = buttons[hitIndex].id==keyHit;
		double e = abs(ms);					// absolute error

	//	rating is a mutiple of accuracy in 40ms steps.
		int r;
		if(timeout)
		{
			r = 5;
		}
		else
		{
//			e += 20.0;							// subtract 20 milliseconds, because of symmetry around zero.
			e /= 42.0;							// convert milliseconds into multiples of 40 milliseconds
			r = int(e)+1;
			if(r > 5)
				r = 5;
		}

		if(correctKey && r == 1)
		{
			int ms_index = round(1000.0*(timeAudio - h.time) + PTT::ms_error_mode) + 512;
			if(ms_index >= 0 && ms_index < 1024)
				PTT::ms_error_count[ms_index]++;
		}

	//----
	//	only remove more than one button, under special conditions
		if(minError < 1.0e9 && !timeout)
		{
		// if the button has still the chance to get a better rating, leave it be
//			if(!correctKey || (e > minError))
			if(!correctKey || (ms < 0 && r!=1))
				break;
		}

		minError = min(minError,e);

		rating = r;
	//----
		ratingCount[rating]++;				// count how often each rating was achieved
		score += scoreTable[rating] / (correctKey ? 1 : 2);

	//----
		buttons[hitIndex].step(timeS);	//	update the buttons position one last time
		buttons[hitIndex].hit = true;		// signal the button to stop moving

	//----
		if(correctKey && rating<=2)
		{
			if(buttons[hitIndex].chanceTime)
			{
				chanceTimeCombo++;
				chanceTimePop.n.n		= min(100*chanceTimeCombo, 5000);
				chanceTimePop.n.s		= buttons[hitIndex].s;
				chanceTimePop.n.s.y	-= 18.0;
				chanceTimePop.n.t0	= timeS;	// start pop in
			}
			else
				chanceTimeCombo = 0;
			combo++;
		}
		else
		{
			combo = 0;
			chanceTimeCombo = 0;
		}

		if			(combo >= 50)	score += 250;
		else if	(combo >= 40)	score += 200;
		else if	(combo >= 30)	score += 150;
		else if	(combo >= 20)	score += 100;
		else if	(combo >= 10)	score +=  50;
				
		int chanceBonus	= min(100*chanceTimeCombo, 5000);
		chanceTimeScore	+= chanceBonus;
		score					+= chanceBonus;

	//----
	//	display combo?
		maxCombo = max(maxCombo,combo);
		if(combo >= 5)
		{
		//----
		//	set combo graphic
			comboNumPopIn.n.s		=
			comboTexPopIn.a.s		= buttons[hitIndex].s;
			comboTexPopIn.a.s.x	+=  6.0;
			comboTexPopIn.a.s.y	-= 14.0;
	//			comboNumPopIn.n.s.x	-=  8.0;
			comboNumPopIn.n.s.y	-= 10.0;
			comboTexPopIn.a.t0	= timeS;	// start now
			comboNumPopIn.n.t0	= timeS;	// start now
		}

	//----
	//	add bubble
		if(correctKey && rating <= 2)
		for(unsigned i = 0; ; i++)
		{
		//----
		//	add a new element?
			if(i == popIn0.size())
			{
				popIn0.resize(i+1);
				popIn0[i].a.tex		= PTT::Bubble;
				popIn0[i].a.duration	= 2.0;
				popIn0[i].fAlpha		= &Alpha1;
				popIn0[i].fSize		= &Size1;
			}

		//----
		//	Use an old (finished) element or as a last resort the new one.
			if(popIn0[i].a.idle())
			{
				popIn0[i].a.s		= buttons[hitIndex].s;
				popIn0[i].a.t0		= h.time - 0.2;	// start not at t0, but 0.2s later
				break;
			}
		}

	//----
	//	add sun flare
		if(correctKey && rating == 1)
		for(unsigned i = 0; ; i++)
		{
		//----
		//	add a new element?
			if(i == popIn1.size())
			{
				popIn1.resize(i+1);
				popIn1[i].a.tex		= PTT::SunFlare;
				popIn1[i].a.duration	= 0.6;
				popIn1[i].fAlpha		= &Alpha2;
				popIn1[i].fSize		= &Size2;
			}

		//----
		//	Use an old (finished) element or as a last resort the new one.
			if(popIn1[i].a.idle())
			{
				popIn1[i].a.s		= buttons[hitIndex].s;
				popIn1[i].a.t0		= h.time;
				break;
			}
		}

	}//handle keypress events


}//InputUpdate()



//-----------------------------------------------------------------------------
//	We want to be able to handle input with > 60Hz, which is not possible
//	in the V-Sync limited graphics and main thread ("PlaySong").
//	Another issue is, that the audio timing resolution is 10ms, which
//	doesn't combine well with the typical refresh rate of 16.68ms.
//	At ~1000Hz on the other hand, we always know exactly which sample is playing.
//	So this thread handles timing, input and game logic, score keeping, etc.
DWORD WINAPI InputThread(LPVOID lpParam)
{
	InitializeCriticalSectionAndSpinCount(&updateIsSafeAccess,800);

//----
//	main loop
	while(!exitThreads)
	{
	//----
	//	main function: input update
		if(TryEnterCriticalSection(&updateIsSafeAccess))
		{
			if(updateIsSafe)
			{
				InputUpdate();
			}

			LeaveCriticalSection(&updateIsSafeAccess);
		}

	//----
	//	secondary function: start or fade out and stop the main song
		if(StopSong && aFile)
		{
			if(aFile->getVolume() <= 0.0f)
				aFile->stop();
			else
				aFile->fadeOut();
		}

	//----
	//	tertiary function: creation and optimization of paths
		if(StartOptimization)
		{
		//----
			if(buttons.size() > 1)
			{
				int populationSize = int(128.0*(1.0+log(2.0+buttons[0].time)));
				OptimizePaths(buttons, populationSize, aspectRatio);
			}
			StartOptimization = false;
		}

	//----
	// No need - but also no easy way to go beyond 1ms resolution which is already ~16 x 60Hz.
	//	At 1000 Hz this thread uses next to no resources, compared to the rest of the program.
		Sleep(threadsSleep);

	}//main loop


//----
//	close old audio and video streams and files
//	aFile->close();
//	vFile->close();
	delete aFile;
	if(aFile!=vFile)
		delete vFile;

//----
//	shouldn't be necessary, as this thread will only exit on program close.
//	if it were necessary, it would actually need to be synchronized.
	aFile = NULL;
	vFile = NULL;

//----
//	cleanup
	DeleteCriticalSection(&updateIsSafeAccess);

//----
//	let the main thread know we're finished
	InputThreadExited = true;
	return 0;
}


//-----------------------------------------------------------------------------
//	This allows us to buffer a few frames ahead, and prevents the main graphics
//	from slowing down, if the video can't be decoded fast enough.
DWORD WINAPI VideoThread(LPVOID lpParam)
{
//----
//	lower priority
//	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
//	SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_BEGIN);
	DWORD sleep;

//----
//	main loop
	while(!exitThreads)
	{
		sleep = threadsSleep;
		if(DoVideoDecoding)
		{
		//----
		//	buffer one frame
			if(vFile)
			{
				if(!vFile->bufferFrame())
					sleep = 10;				// sleep a little longer, when buffer is full
			}
		}
		else sleep = 10;

		Sleep(sleep);
	}

//----
//	let the main thread know we're finished
	VideoThreadExited = true;
	return 0;
}



//-----------------------------------------------------------------------------
void ExitInputAndVideoThread()
{
//	let the input and video threads know they're no longer needed
	exitThreads = true;

//----
//	wait for preview thread to exit
	while(VideoThreadExited==false && InputThreadExited==false)
		Sleep(1);
}





//-----------------------------------------------------------------------------
//	The main thread, does file and variable initialization and drawing.
void PlaySong()
{
//----
	static const	TiledTexture	Ratings	(L"graphics/ratings.png",		1, 1, 8),
											numbers2	(L"graphics/numbers2.png",	L'0', 12, 1, 0.7f),	// {0="0", 1="1", ..., 9="9", 10="+", 11="-"}
											texCombo	(L"graphics/combo.png",			0, 1),
											ctStart	(L"graphics/ctstart.png",		0, 1),
											ctEnd		(L"graphics/ctend.png",			0, 1);
	static const	Texture			texVolume(L"graphics/volume.png"),
											jiku		(L"graphics/jiku.png"),
											note		(L"graphics/note.png"),
											perfect	(L"graphics/perfect.png"),
											great		(L"graphics/great.png"),
											standard	(L"graphics/standard.png"),
											failed	(L"graphics/failed.png"),
											bonusMode(L"graphics/bonus mode.png"),
											newHghscr(L"graphics/new highscore.png");
	static			Texture			texBG;

	FlyingButton::tex = Buttons2;

	if(!SelectedSong)
		return;

//----
	{
		Index idx(1024);
		idx.index(PTT::ms_error_count, 1024, true);
		DWORD count0 = PTT::ms_error_count[idx[0]],
				count1 = PTT::ms_error_count[idx[1]],
				count2 = PTT::ms_error_count[idx[2]];
//		if(count2 > 10)
		{
			double	ms0 = idx[0] - 512.0,
						ms1 = idx[1] - 512.0,
						ms2 = idx[2] - 512.0,
						num = 0,
						den = 0;
			for(int i = 1; i < 1024; i++)
			{
				double ms = i - 512.0,
						 weight = sqr(double(PTT::ms_error_count[i]));
				num += weight * ms;
				den += weight;
			}
//			PTT::ms_error_mode = (count0*ms0 + count1*ms1) / double(count0+count1);
			if(den > 1000.0)
				PTT::ms_error_mode = num / den;
			else
				PTT::ms_error_mode = 40.0;
			std::cout << "ms_error_mode = " << ms_error_mode << " (ms["<<count0<<"] = " << ms0 << ", ms["<<count1<<"] = " << ms1 << ", ms["<<count2<<"] = " << ms2 << ")" << std::endl;
		}
	}

//----
//	Stop preview song
	StopPreview = true;

	const	OsuFile &osu = *SelectedSong;

//----
// get threads ready
	threadsSleep = 1;
	StopSong = false;

//----
//	close old audio and video streams and files
	delete aFile;
	if(aFile!=vFile)
		delete vFile;

//----
//	This is important, as it's checked for below and by the video thread.
	aFile = NULL;
	vFile = NULL;

//----
//	Open new audio and video files
	if			(osu.videoFilename.size())						vFile = new MediaFile(PTT::audio, osu.videoFilename, 10, 0.01f*SongVolume, true);
	if			(osu.audioFilename == osu.videoFilename)	aFile = vFile;
	else if	(osu.audioFilename.size())						aFile = new MediaFile(PTT::audio, osu.audioFilename, 20, 0.01f*SongVolume, true);
	if			(osu.backgroundFilename.size())				texBG.setData(GraphicsFile(osu.backgroundFilename.c_str()));
	else																texBG.setData(*defaultBG);

//----
//	Let the thread prebuffer the video
	DoVideoDecoding = (vFile!=NULL);

//----
// Setup time-averaged log-frequency bins
/*	ITF<float>::alpha = 0.2;
	ITF<float> itf;
	const  Mem2D<float> &bins = itf.getData();
	static Mem2D<float> data;
	double itfFrameRate = 60000.0/1001.0;
	uint				samples		= aFile->audioFrameSize(),
						channels		= aFile->getNumChannels();
	int16_t*			buffer		= aFile->getBuffers();
	double			sampleRate	= double(aFile->getSampleRate()),
						length		= double(samples) / sampleRate;
	int				frames		= int(length*itfFrameRate+0.5);

//	ITF frequencies are in 1/sample, not 1/second
	itf.setup(6.875/sampleRate, 28160.0/sampleRate, frames);

	double	f0 = sampleRate*itf.frequencyOfIndex(0),
				f1 = sampleRate*itf.frequencyOfIndex(144);

	data.resize(samples,5);
	data.zero();

	float		*y = &data(0,0),				// signal
				*H = &data(0,1),				// high frequency proper rotation component
				*L = &data(0,2),				//	low  frequency baseline signal
				*f = &data(0,3),				//	frequencies
				*A = &data(0,4);				//	amplitudes
	double	P, P_prev = 0.0;				// total power of the current and previous component

	for(uint channel = 0; channel < channels; channel++)
	{
		for(uint i = 0; i < samples; i++)
			y[i] = buffer[channels*i+channel];

		for(int components = 0; components < 20; components++)
		{
					ITF<float>::extract		(y, L, H, samples);
			P =	ITF<float>::transform	(H, f, A, samples);
					itf.accumulate				(   f, A, samples);
					std::swap					(y, L);					// continue with baseline signal

//			if(abs(P-P_prev) <= 0.001*P)
//				break;

			P_prev = P;
		}
	}

	itf.rescaleAmplitudes(1.0);

//	for(uint i = 0; i < samples; i++)
//		buffer[2*i] = buffer[2*i+1] = y[i];
*/
//----
	for(int i = 0; i < 4; i++)
		drum[i]->setVolume(0.01f*EffectsVolume[i]);

//----
	LONG		borderWidth			= 48;
	float		borderWidthf		= float(borderWidth);
	float		bgScale				= 1.0f;	// background scale
	int		breakPeriodIndex	= 0;
	int		lastFrame			= -1;		// last displayed frame of videostream
	DWORD		highscore, greatLimit = 0;
	{
		DWORD	highscore1, highscore2;
		GetHighscore(highscore1, highscore2);
		highscore = PTT::BonusMode ? highscore2 : highscore1;
	}

//----
//	Initialize global variables
				hideScore			= false;
				hitIndex				= 0;
				paused				= true;
				cheated				= false;
				correctKey			= false;	// did the player hit the correct button, the last time? Corresponds to "rating".
				rating				= 0;		// last rating index [0,6)
				score					= 0;		// current total score
				combo					= 0;		// current combo
				maxCombo				= 0;		// max combo
				drawVol				= 0;		// time to display volume
				chanceTimeStart	= -1,		// time in milliseconds, when chance time starts
				chanceTimeStop		= -1;		// time in milliseconds, when chance time is over
				chanceTimeCombo	= 0;		// current chance time combo
				chanceTimeScore	= 0;		// total chance time score
				inputOver			= false;	// whether there are no more buttons to press, left.
				exitPlaySong		= false;	// we shall return
				volume				= MasterVolume;

	memset(ratingCount,0,sizeof(ratingCount));// total number of different ratings

	vibration.wLeftMotorSpeed	= 0;		// The vibration information to send to the controller
	vibration.wRightMotorSpeed	= 0;		// The vibration information to send to the controller
	srand(*(int*)&osu.metadata.title[0]);// we always use the same sequence of "random" numbers, for each song title

//----
//	Setup positions of buttons from osu file.
// Speed and direction must be determined algorithmically.
	buttons.resize(0);									// destroy old stuff
	buttons.reserve(2*osu.hitObjects.size());		// make room for the new points (sliders will be converted to two or more points)
	std::vector<int>	indices1(osu.hitObjects.size()/2),
							indices2(osu.hitObjects.size()/2);

//----
//	The longest kiai period is our chance time
	for(int i = osu.timingPoints.size()-1; i > 0; i--)
	{
		if(osu.timingPoints[i].kiaiMode == TRUE)
		{
			int stop = (i==osu.timingPoints.size()-1 ? osu.hitObjects[osu.hitObjects.size()-1].time : osu.timingPoints[i+1].time);
			int length = stop - osu.timingPoints[i].time;
			if(length > (chanceTimeStop-chanceTimeStart))
			{
				chanceTimeStart = osu.timingPoints[i].time;
				chanceTimeStop = stop;
			}
		}
	}

	double	bpm	= 0,
				speed	= 0,
				time	= 0,
				dt		= 0;
	char		buttonChoices	= min(4, 1+osu.overallDifficulty/2),
				id = 0;
	int		numChanceTime	= 0;

	if(osu.timingPoints.size())
		bpm = 60000.0 / osu.timingPoints[0].msPerBeat;
	speed = bpm;

//	if(osu.overallDifficulty > 3)	speed *= 1.5;
//	if(osu.overallDifficulty > 6)	speed *= 1.5;
	speed *= double(osu.sliderMultiplier);

//----
//	Collect all buttons into an array of drawables
	for(unsigned i = 0; i < osu.hitObjects.size(); i++)
	{
		const HitObject &o = osu.hitObjects[i];
		time = 0.001*o.time;

		if(o.type & SPINNER)					// spinner are usually not on a beat
			continue;

		if(o.type & NEW_COMBO)
		{
			char newID;
			while(id == (newID = rand() % buttonChoices) && buttonChoices>1)
				;

			id = newID;
		}

		if(o.type & SLIDER && o.points.size())
		{
			double beats = o.stop / (100.0*osu.sliderMultiplier);
			dt = 0.001 * beats * osu.timingPoints[0].msPerBeat;
		}

	//	add the first point and one for every slider count
		int limit = PTT::BonusMode ? o.count : 0;
		for(int k = 0; k <= limit; k++, time+=dt)
		{
			int n = buttons.size();
			buttons.resize(n+1);
			FlyingButton &b = buttons[n];

			b.s.x =
			b.s.y = -1.0e9;
			b.v.x = speed;
			b.v.y = 0;
			b.id = id;
			b.chanceTime = (o.time >= chanceTimeStart-50 && o.time <= chanceTimeStop+50);
			b.newCombo = (o.type & NEW_COMBO)!=0 && k==0;
			b.time = float(time);
			b.size = 1.0f;
			if(b.chanceTime)
				numChanceTime++;
		}
	}

//----
//	create dummy button
	if(buttons.size()==0)
	{
		buttons.resize(1);
		buttons[0].time = float(aFile->getDuration());
		buttons[0].s.x = 1.0e9;
		buttons[0].path.end.x = 1.0e9f;
	}

//----
//	Sort buttons by time. Usually they're already sorted - if not, there are probably bigger problems with the map.
	std::sort(buttons.begin(), buttons.end());

//----
	if(vFile)
	{
		if(vFile->getHeight()==1080)	aspectRatio = 1.777777777f;
		else									aspectRatio = vFile->getWidth() / float(vFile->getHeight());
	}
	else										aspectRatio = texBG.getWidth() / float(texBG.getHeight());

//----
//	Let the thread optimize the paths
	StartOptimization	= true;

//----
//	Calculate limit for greatness
	int chanceCombo = 0;
	for(unsigned i = 0; i < buttons.size(); i++)
	{
		if(buttons[i].chanceTime)
		{
			chanceCombo++;
			greatLimit += min(65u*chanceCombo+300, 4500u);
		}
		else
		{
			greatLimit += min(i*40u, 500u);
		}
	}

//----
//	Pop Ins, maybe this should be done with a constructor or initializer...
	popIn0.resize(0);
	popIn1.resize(0);
	popIn0.reserve(buttons.size());			// a reallocation could be bad, as they are unsynchronized...
	popIn1.reserve(buttons.size());			// ...but we make sure there's enough space, so nothing can happen.
	comboTexPopIn.a.tex			= &texCombo;
	comboNumPopIn.n.tex			= &numbers2;
	chanceTimePop.n.tex			= &numbers2;
	comboTexPopIn.a.centered	= false;
	comboTexPopIn.a.duration	= 0.5;		// "animation" play time length
	comboTexPopIn.fAlpha			= &Alpha3;	// alpha function
	comboTexPopIn.ani->t0		= Animated::IDLE;
	comboNumPopIn.n.duration	= 0.5;
	comboNumPopIn.fAlpha			= &Alpha3;
	comboNumPopIn.fSize			= &Size3;	// size function: only for number
	comboNumPopIn.n.hAlign		= align::right;
	comboNumPopIn.n.vAlign		= align::center;
	comboNumPopIn.c				= Color(240,165,0);
	comboNumPopIn.additiveColor= true;
	comboNumPopIn.ani->t0		= Animated::IDLE;
	chanceTimePop.n.duration	= 1.8;
	chanceTimePop.n.v.y			= -20.0;
	chanceTimePop.n.hAlign		= align::centered;
	chanceTimePop.n.size			= 0.8f;
	chanceTimePop.fAlpha			= &Alpha3;
	chanceTimePop.n.sign			= true;
	chanceTimePop.c				= Color(160,160,160);
	chanceTimePop.additiveColor= true;
	chanceTimePop.ani->t0		= Animated::IDLE;

	chanceTimeInfoPopIn.ani->duration = 4.0;
	chanceTimeInfoPopIn.a.s.x	= 256.0;
	chanceTimeInfoPopIn.a.s.y	= 384.0;//328.0;
	chanceTimeInfoPopIn.a.centered = true;
	chanceTimeInfoPopIn.a.tex	= &ctStart;
	chanceTimeInfoPopIn.fAlpha	= &Alpha3;
	chanceTimeResultPop.fAlpha	= &Alpha3;
	chanceTimeResultPop.n.hAlign= align::centered;
	chanceTimeResultPop.n.vAlign= align::center;
	chanceTimeResultPop.c				= Color(253,131,212);
	chanceTimeResultPop.n.tex			= &numbers2;
	chanceTimeResultPop.additiveColor= true;
	chanceTimeResultPop.ani->s.x		= 256.0;
	chanceTimeResultPop.ani->s.y		= 352;//296.0;
	chanceTimeResultPop.n.size			= 1.0f;
	chanceTimeResultPop.n.duration	= 4.0;
	chanceTimeInfoPopIn.ani->t0 = (numChanceTime ? 0.001*chanceTimeStart : Animated::IDLE);
	chanceTimeResultPop.ani->t0 = (numChanceTime ? 0.001*chanceTimeStop  : Animated::IDLE);
	chanceTimeInfoPopIn.ani->t = Animated::IDLE;
	chanceTimeResultPop.ani->t = Animated::IDLE;

//----
	double	lastTime	= 0,					// To get time of the last frame
				timeS		= 0;					// current song play time in seconds

// start the shared timer with offset if applicable
	timer.set(-0.001*osu.audioLeadIn - 0.001*PTT::ms_error_mode);
	__int64 startTicks = timer.resultTicks();

//----
//	let the input thread know it's safe to start updates, now.
	EnterCriticalSection(&updateIsSafeAccess);
	updateIsSafe = true;
	LeaveCriticalSection(&updateIsSafeAccess);

	bool videoStarted = false;

//----
//	Main loop
	while(!wnd->isQuitting() && !exitPlaySong)
	{
	//----
	//	While the input thread is still busy optimizing we must update inputs ourselves
		if(StartOptimization)
			InputUpdate();

		UpdateCaption();

	// if not paused, get song play time in seconds from our shared and adjusted counter, else get the pause time.
		if(paused)	timeS = timer.result();					// stop time
		else			timeS = timer;								// current time

		double frameTime = timeS - lastTime;
		lastTime = timeS;

	//----
	//	since the frame is not displayed yet, and it will be displayed for some time, we draw a little ahead
//		timeS += 0.5 * frameTime;

	//----
	//	time for optimization to finish?
		if(timer.resultTicks() > startTicks && timeS > buttons[0].time-6.0)
			StopOptimizing = true;

	//----
		texBG.bind();

		if(vFile)
		{
			int	currFrame = int((timeS - 0.001*osu.videoStartTime) * vFile->getFrameRate()),
					w = vFile->getWidth(),
					h = vFile->getHeight();
			while(lastFrame < currFrame)
			{
				AVFrame *frame = vFile->getNextFrame();

				if(frame == NULL)
					break;

				if(++lastFrame == currFrame)
				{
					texBG.setData(w, h, GL_RGB, frame->data[0]);
					videoStarted = true;
				}

			//	mark frame for reuse
				frame->pts = -1;

			//----
			//	stretch anamorph 1080p
//				if(bgH==1080)
//					texBG.setData(1920,1080,0,NULL);
			}
		}

	//----
		RECT window;
		LONG wndW, wndH;
		{
			GetClientRect(wnd->getHWND(),&window);
			wndW = window.right;
			wndH = window.bottom;

		//----
		//	Stretch background to window, while keeping aspect ratio.
		//	If there is a video, use its aspect ratio instead of the graphic, to avoid a switch when the video starts.
			UINT	w = vFile ? vFile->getWidth()  : texBG.getWidth(),
					h = vFile ? vFile->getHeight() : texBG.getHeight();

		//----
		//	stretch anamorph 1080p
			if(h == 1080)
			{
				w =  1920;
				texBG.setData(1920,1080);
			}

			if(HUD_mode == 2)
				h += UINT(1.0f/bgScale*2.0f*borderWidthf+0.5f);

			float	bgW		= float(w),
					bgH		= float(h),
					scaleX	= float(wndW) / bgW,
					scaleY	= float(wndH) / bgH;

			RECT bgRect;
			if(scaleX < scaleY)					//	need to pad top and bottom
			{
				bgRect.left	= 0;
				bgRect.top	= int((0.5f*(wndH-scaleX*bgH))+0.5f);
			}
			else										//	need to pad left and right
			{
				bgRect.left	= int((0.5f*(wndW-scaleY*bgW))+0.5f);
				bgRect.top	= 0;
			}

			bgRect.right	= wndW - bgRect.left;
			bgRect.bottom	= wndH - bgRect.top;
			w = bgRect.right  - bgRect.left,
			h = bgRect.bottom - bgRect.top;

		//----
		//	Use the background as our effective "client window", making no drawing outside possible.
			glViewport(bgRect.left, bgRect.top, w, h);
			glMatrixMode(GL_PROJECTION);
			glLoadIdentity();
			glOrtho(0, w, h, 0, -1.0, 1.0);
			glMatrixMode(GL_MODELVIEW);
			glLoadIdentity();

			window.left		= 0;
			window.top		= 0;
			wndW = window.right	= w;
			wndH = window.bottom	= h;
		}

	//----
		glClearColor(0, 0, 0, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		white.glApply();
		bgScale = min(float(wndW)/texBG.getWidth(), float(wndH)/texBG.getHeight());
		texBG.draw(0.5f*wndW, 0.5f*wndH, bgScale, true);

	//----
	//	Visualization, e.g., waveform or spectrum
		if(visualMode && !aFile->endOfFile())
		{
		//----
		//	block buffers as long as they're used for visualization
			aFile->setBlockBuffers(true);

			const int16_t *pcm			= aFile->audioBuffers();			// all pcm buffers
			const unsigned	sBuffer		= aFile->audioBufferSize(),		// maximum samples per buffer
								sFrame		= aFile->audioFrameSize(),			// actual  samples per buffer and frame
								channels		= aFile->getNumChannels(),
								numBuffers	= aFile->getNumBuffers(),			// total number of buffers
								sTotal		= numBuffers * sBuffer,				// samples in all buffers
								sActual		= numBuffers * sFrame,				// samples in all frames
								n				= visualSamples,						// a local variable, to get around synchronization
//								useITF		= visualITF,							// a local variable, to get around synchronization
								useITF		= false,									// a local variable, to get around synchronization
								currMode		= visualMode | (useITF<<2);

//			if(useITF)
//				n = 2*(bins.m()-1);													// Using ITF instead of FFT
			const double	f			= double(aFile->getSampleRate()),
								df			= f / n,										// lowest  frequency and frequency resolution in Hz
								fNyquist	= f * 0.5,									// highest frequency in Hz (found at index visualSamples/2)
								offsetX	= n * 0.25,
								scaleX	= (n+offsetX) / log(fNyquist);

			static Mem2D<float> vertexArray;
			static Mem2D<Color> spectrogram;
			static int prevMode = currMode;
			if(prevMode != currMode || vertexArray.m() != 16*n)
			{
				prevMode = currMode;

				vertexArray.resize(16*n);
				vertexArray.zero();
				spectrogram.zero();
			//----
			//	precalculate x coordinates
				for(int x = 0; x < n; x++)
				{
					vertexArray[4*x]   =
					vertexArray[4*x+2] = float(visualMode==1||useITF ? x : (x ? scaleX * log(x*df) : 0) - offsetX);
				}
			//----
			//	precalculate weighting function
				for(int x = 0; x <= n/2; x++)
				{
					double	f	= double(x)*df,
//					double	f	= useITF ? itf.frequencyOfIndex(x) * sampleRate : double(x)*df,
								f2	= sqr(f),
								f4 = sqr(f2),
								RA = sqr(12220.0) * f4 / ((f2+sqr(20.6)) * sqrt((f2+sqr(107.7))*(f2+sqr(737.9))) * (f2+sqr(12200.0)));
					vertexArray[12*n+x] = float(RA);
//					vertexArray[12*n+x] = 2.0f + 20.0f*log10f(RA);
//					vertexArray[12*n+x] = 1.0f;
				}
			//----
			//	precalculate hann window function
				float half	= 0.5f * n,
						d = float(2.0 * PI / n);
				int	x;
				for(x = 0; x <= n/2; x++)
				{
					float x_	= x - half,
							w	= 0.5f*(1.0f+cosf(x_*d));
					vertexArray[13*n+x] = w;
					vertexArray[14*n-x] = w;					//	second half is simply mirrored
				}
			}

		//----
			float rgb[3];
			if(visualHue >= 0)	ColorConversion::HSVtoRGB(visualHue, 1.0f, 1.0f,  rgb);
			else						ColorConversion::HSVtoRGB(0.0f, 0.0f, -visualHue, rgb);

			float *colorArray	= &vertexArray[4*n],
					*re			= &vertexArray[10*n],
					*im			= &vertexArray[11*n],
					*A				= &vertexArray[12*n],		// weighting function
					*window		= &vertexArray[13*n];		// window function

			bool accessedBuffer[64] = {false};			// was the buffer accessed?
			if(!useITF && sFrame)
			{
				const
				int64_t sample = aFile->playedSamples();	// last played sample
//				int64_t sample = int64_t(timeS * aFile->getSampleRate() + 0.5);

				for(int64_t x = 0; x < n; x++)
				{
					const
					int64_t	s			= sample + x - n/2,	// sample to be rendered from left to right
								frame		= s / sFrame,			// number of previously finished frames
								buf		= frame % numBuffers,// current frame buffer
								i			= s % sFrame + buf * sBuffer;	// offset in samples from the beginning of pcm_data
					if(s < 0)
						continue;
					accessedBuffer[buf] = true;

					float y = 0.000030517578125f * 0.5f*(float(pcm[2*i]) + float(pcm[2*i+1]));
					vertexArray[4*x+1] =
					re[x] = y * window[x];
					im[x] = 0;
					y = abs(y);
					float r2 = 0.5f * y * rgb[1],
							b2 = 0.5f * y * (1.0f-rgb[2]);
					colorArray[6*x  ] = rgb[0]+r2;
					colorArray[6*x+1] = rgb[1];
					colorArray[6*x+2] = rgb[2]+b2;
					colorArray[6*x+3] = r2;
					colorArray[6*x+4] = 0;
					colorArray[6*x+5] = b2;
				}
			}

		//----
		//	only buffers that were not accessed can be reused
			for(unsigned i = 0; i < aFile->getNumBuffers(); i++)
			{
				if(!accessedBuffer[i])
					aFile->unblockBuffer(i);
			}

		//----
		//	FFT
			int count = n;
			static int col = 0;
			if(!paused)
			{
				if(++col >= 128)
					col = 0;
			}

			float r,g,b,r2,b2;
			if(visualMode!=1)
			{
				count = n/2;
				if(!useITF)
				{
					DWORD m;
					_BitScanForward(&m, n);
					FFT(m, re, im, 1);
				}

				if(spectrogram.m() != n)
				{
					spectrogram.resize(n, 128);
					spectrogram.zero();
				}
				Color *curr = &spectrogram(0,col),
						*prev = &spectrogram(0,(col+127)%128),
						*border = (Color*)&vertexArray[14*n];

//				int itfFrame = int(timeS*itfFrameRate);
//				itfFrame = max(0,min(bins.n()-1,itfFrame));

				for(int x = 0; x < count; x++)
				{
					float		y;
					float		P2	= sqr(re[x+1]) + sqr(im[x+1]);
//					double	P	= 0.25*bins(x,itfFrame-1) + 0.5*bins(x,itfFrame) + 0.25*bins(x,itfFrame+1);
//					double	P	= bins(x,itfFrame);
//					if(useITF)
//						P2 = sqr(0.04f*bins(x,itfFrame));

					if(visualMode == 2)
					{
//						y = P;
//						y = log10(1.0 + sqr(P));
						y = 42.0f*A[x]*log10f(1.0f + 42.0f*P2) * 0.001953125f * float(n);
						vertexArray[4*x+1] = max(-y, -0.7639320225f);
					}
					else
					{
					// dBFS
						y = 10.0f*log10f(P2*A[x]);
						vertexArray[4*x+1] = -2.0f - 0.02f*y;
					}

					y = abs(y);
					r2 = visualMode == 2 ? 0.5f * y * rgb[1]        : 0;
					b2 = visualMode == 2 ? 0.5f * y * (1.0f-rgb[2]) : 0;
					r	= rgb[0]+r2;
					g	= rgb[1];
					b	= rgb[2]+b2;
					colorArray[6*x  ] = r;
					colorArray[6*x+1] = g;
					colorArray[6*x+2] = b;
					colorArray[6*x+3] = r2;
					colorArray[6*x+4] = 0;
					colorArray[6*x+5] = b2;

				//----
				//	spectrogram
/*					ColorConversion::LCHUVtoRGB(10.0f*pow(P,0.25), 50.0f, 20.0f, rgb);
					curr[2*x].R = rgb[0]*255.0f;
					curr[2*x].G = rgb[1]*255.0f;
					curr[2*x].B = rgb[2]*255.0f;
*/
					curr[2*x].R = BYTE(min(255.0f,y*255.0f*(r+r2)));
					curr[2*x].G = BYTE(min(255.0f,y*255.0f*g));
					curr[2*x].B = BYTE(min(255.0f,y*255.0f*(b+b2)));
					curr[2*x].A = 255;//BYTE(min(255.0f,255.0f*y));
					curr[2*x+1] = prev[2*x];
					border[2*x+1] = curr[2*x];
				}
			}

			glPushMatrix();
			glDisable(GL_TEXTURE_2D);
			glEnableClientState(GL_VERTEX_ARRAY);
			glEnableClientState(GL_COLOR_ARRAY);

			if(visualMode == 2){	glRotatef(-90.0f, 0, 0, 1.0f);
										glTranslatef(-1.0f*wndH, 1.0f*wndW, 0);
										glScalef(wndH/float(n-1), 0.50f*wndW, 1.0f);
			}
			else
			{
			if(visualMode == 1)	glTranslatef(0, 0.5f*wndH,  0);
			if(visualMode == 3)	glTranslatef(0, float(wndH),0);
										glScalef(wndW/float(n-1), 0.40f*wndH, 1.0f);
			}

			if(useITF)				glScalef(2.0f, 1.0f, 1.0f);

			glBlendFunc(GL_SRC_COLOR, GL_ONE);
			glVertexPointer(2, GL_FLOAT, 0, vertexArray);
			glColorPointer (3, GL_FLOAT, 0, colorArray);
			glDrawArrays(GL_TRIANGLE_STRIP, 0, 2*count);

			if(visualMode!=2)
			{
				glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
				glLineWidth(2.0f);
				glDisableClientState(GL_COLOR_ARRAY);
				glColor4ub(0,0,0,128);
//				glColor4f(r,g,b,0.5f);
				glVertexPointer(2, GL_FLOAT, 4*sizeof(float), vertexArray);
//				glColorPointer (3, GL_FLOAT, 6*sizeof(float), colorArray);
				glDrawArrays(GL_LINE_STRIP, 0, count);
				glBlendFunc(GL_SRC_COLOR, GL_ONE);
			}


		//----
			if(visualMode==2)
			{
				float dy = 1.2360679775f/128.0f;
				for(int x = 0; x < count; x++)
	//			for(int x = 0; x < 2*count; x++)
				{
					vertexArray[4*x+1] = 0;
					vertexArray[4*x+3] = -dy;
				}

				glEnableClientState(GL_COLOR_ARRAY);
				glVertexPointer(2, GL_FLOAT, 0, vertexArray);
				
				glTranslatef(0.0f, -0.7639320225f, 0);
	//			glScalef(0.8f, 1.2f, 1.0f);

			//----
			//	draw border
				glTranslatef(0, dy, 0);
				glColorPointer(4, GL_UNSIGNED_BYTE, 0, &vertexArray[14*n]);
			// we cheat a little to save bandwidth: 2*count would be fullscale; but the frequencies above 11kHz are pretty unimportant and even ugly because very compressed
			// of course there are other ways that would be a lot faster, like replacing the triangle strip with a texture.
				glDrawArrays(GL_TRIANGLE_STRIP, 0, 2*count);
//				glDrawArrays(GL_TRIANGLE_STRIP, 0, 2*count);
				for(int y = spectrogram.n(); y > 0; y--)
				{
					glTranslatef(0, -dy, 0);
					glColorPointer(4, GL_UNSIGNED_BYTE, 0, &spectrogram(0,(y+col)%128));
//					glDrawArrays(GL_TRIANGLE_STRIP, 0, count);
					glDrawArrays(GL_TRIANGLE_STRIP, 0, 2*count);
				}
			}
			else
			{
			}


			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
			glDisableClientState(GL_COLOR_ARRAY);
			glEnable(GL_TEXTURE_2D);
			glPopMatrix();
		}
		else
		{
		//----
		//	stop buffer blocking
			aFile->setBlockBuffers(false);
		}

	//----
	//	Draw the action inside the background (e.g.,video), but not inside the border.
		float	safeLeft		= window.left + 0.08f*wndH,
				safeTop		= window.top  + max(0.08f*wndW, borderWidthf+0.04f*wndW),
				safeWidth	= wndW - 2.0f*safeLeft,
				safeHeight	= wndH - 2.0f*safeTop;
		Movable::sx = safeWidth   / 512.0f,					// scaling factors are derived from osu's coordinate system
		Movable::sy = safeHeight  / 384.0f;
		Movable::scale = 384.0f/1080.0f*Movable::sy;		// a window height of 1080 will give 1:1 pixel mapping
//		Movable::scale = 512.0f/1920.0f*Movable::sx;		// a window width  of 1920 will give 1:1 pixel mapping

		glPushMatrix();
		glTranslatef(safeLeft, safeTop, 0);
		{
		//----
		//	update position of all flying buttons
			if(!paused)
				for(unsigned i = 0; i < buttons.size(); i++)
					buttons[i].step(timeS);

		//----
		//	draw gray buttons and save indices of the other drawables
			indices1.resize(0);					// jiku (clock-hand) indices
			indices2.resize(0);					// flying buttons indices
			white.glApply();
//			double timeFactor = 1000.0 / osu.timingPoints[0].msPerBeat;
			double timeFactor = bpm / 60.0;
			for(int i = buttons.size()-1; i >= hitIndex; i--)
			{
				float		margin	= 256.0f,
							x			= FlyingButton::sx * (float)buttons[i].s.x,
							y			= FlyingButton::sy * (float)buttons[i].s.y,
							angle		= float(45.0 * (timeS - buttons[i].time) * timeFactor);

				if(x > -margin && y > -margin && x < wndW+margin && y < wndH+margin)
					indices2.push_back(i);

				if(angle >= -360.0f && angle <= 22.5f)
				{
					x = FlyingButton::sx * buttons[i].path.end.x;
					y = FlyingButton::sy * buttons[i].path.end.y;
					if(buttons[i].id==3)
						y -= 0.09f*FlyingButton::scale*FlyingButton::tex->getHeight();					// adjust sankaku placement
					float scale = 1.0f;
					if(angle <= -320.0f)		scale += 0.4f * (1.0f-sqr((340.0f+angle)*0.05f));		// "pop in" and out effect
					else if(angle >= 0)		scale = 1.0f - sqr(angle/22.5f);								// "pop out" effect

					Buttons1->draw( buttons[i].id, x, y, scale*FlyingButton::scale, true);	
					indices1.push_back(i);
				}

			}

		//----
		//	draw jiku; almost the same as above + rotation
			for(unsigned k = 0; k < indices1.size(); k++)
			{
				int i = indices1[k];
				float	x		= FlyingButton::sx * buttons[i].path.end.x,
						y		= FlyingButton::sy * buttons[i].path.end.y,
						angle	= float(45.0 * (timeS - buttons[i].time) * timeFactor);
				float scale = 1.0f;
				if(angle <= -320.0f)		scale += 0.4f * (1.0f-sqr((340.0f+angle)*0.05f));
				else if(angle >= 0)		scale = 1.0f - sqr(angle/22.5f);

				glPushMatrix();
				glTranslatef(x, y, 0);
				glRotatef(angle, 0, 0, 1.0f);
				jiku.draw(0, 0, scale*FlyingButton::scale, true);
				glPopMatrix();
			}


		//----
		//	draw trails
			for(unsigned i = 0; i < buttons.size(); i++)
			{
				buttons[i].drawTrail();
			}

		//----
		//	draw visible and remaining flying buttons
			for(unsigned k = 0; k < indices2.size(); k++)
			{
				int i = indices2[k];
				buttons[i].draw();
			}

		//----
		//	draw sun glares
			for(unsigned i = 0; i < popIn1.size(); i++)
			{
				popIn1[i].step(timeS);
				popIn1[i].draw();
			}

		//----
		//	draw bubbles
			for(unsigned i = 0; i < popIn0.size(); i++)
			{
				popIn0[i].step(timeS);
				popIn0[i].draw();
			}

		//----
		//	draw combo
			comboNumPopIn.step(timeS);
			comboTexPopIn.step(timeS);
			if(combo >= 5)
			{
				comboNumPopIn.n.n = combo;
				comboNumPopIn.draw();
				comboTexPopIn.draw();
			}

			chanceTimePop.step(timeS);
			chanceTimeInfoPopIn.step(timeS);
			chanceTimeResultPop.step(timeS);
			chanceTimePop.draw();
			chanceTimeInfoPopIn.draw();
	
			if(!chanceTimeResultPop.ani->idle())
			{
				chanceTimeResultPop.n.n = chanceTimeScore;
				chanceTimeResultPop.draw();
			}

			if(numChanceTime && timeS > 0.001*chanceTimeStart && chanceTimeInfoPopIn.a.idle())
			{
				chanceTimeInfoPopIn.a.tex = &ctEnd;
				chanceTimeInfoPopIn.a.t0 = 0.001*chanceTimeStop;
			}

		}
		glPopMatrix();

	//----
	//	chance time?
		float borderAlpha = 0.5f;
		bool	chanceTime = (timeS >= 0.001f*chanceTimeStart && timeS <= 0.001f*chanceTimeStop);
		if(chanceTime && !HUD_mode)
			borderAlpha = 1.0f;

	//----
	//	draw borders
		glLineWidth(1.0f);
		if(HUD_mode==0 || chanceTime)
		{
			glColor4f(0.0f, 0.0f, 0.0f, borderAlpha);
			DrawRect(0, 0, wndW, borderWidth);
			DrawRect(0, wndH-borderWidth, wndW, wndH);
			glDisable(GL_TEXTURE_2D);
			glColor4ub(0, 0, 0, 200);
//			black.glApply();
			glBegin(GL_LINES);
			glVertex2f(0,           borderWidthf+0.5f);
			glVertex2f(float(wndW), borderWidthf+0.5f);
			glVertex2f(0,           wndH-borderWidthf-0.5f);
			glVertex2f(float(wndW), wndH-borderWidthf-0.5f);
			glEnd();
			glEnable(GL_TEXTURE_2D);
		}

	//----
		if(chanceTime)
		{
			white.glApply();
			Ratings.draw(7, 36.0f, wndH-42.0f, 0.5f);
		}

	//----
	//	draw final scores
		if(inputOver)
		{
			if(!hideScore)
			{
				float	x1	= 0.5f*wndW-0.3f*wndH,
						x2	= 0.5f*wndW+0.3f*wndH,
						y	= 0.25f*wndH,
						dy	= 0.05f*wndH,
						dy2= 0.3f*dy;
				glColor4ub(0, 0, 0, 128);
				RECT bg = {int(x1)-32, 0, int(x2)+36, wndH};
				if(HUD_mode==0)
				{
					bg.top    += (borderWidth+1);
					bg.bottom -= (borderWidth+1);
				}
				DrawRect(bg);
				glColor4ub(0, 0, 0, 200);
				glBegin(GL_LINES);
				float y1 = bg.top    + 0.5f,
						y2 = bg.bottom + 0.5f;
				glVertex2f(bg.left -0.5f, y1);
				glVertex2f(bg.left -0.5f, y2);
				glVertex2f(bg.right+0.5f, y1);
				glVertex2f(bg.right+0.5f, y2);
				glEnd();
				white.glApply();

			//----
				float s = 0.75f*Movable::scale;
				if(ratingCount[5]==0 && ratingCount[4]==0 && ratingCount[3]==0)	perfect.draw	(0.5f*wndW, 0.18f*wndH, s, true);
				else if(score > int(greatLimit))												great.draw		(0.5f*wndW, 0.18f*wndH, s, true);
				else if(score > 0.5*greatLimit)												standard.draw	(0.5f*wndW, 0.18f*wndH, 0.9f*s, true);
				else																					failed.draw		(0.5f*wndW, 0.18f*wndH, s, true);

				for(int n = 1; n < 6; n++, y+=dy)
				{
					Ratings.draw(n, x1, y, s);
					numbers2.drawNumber(ratingCount[n], x2, y-dy2, s, align::right, align::top);
				}

				Ratings.draw(6, x1, y, s);						numbers2.drawNumber(maxCombo,        x2, y-dy2,      s);
				Ratings.draw(7, x1, y+=dy, s);				numbers2.drawNumber(chanceTimeScore, x2, y-dy2,      s);
				Ratings.draw(8, x1, y+=1.5f*dy, 1.5f*s);	numbers2.drawNumber(score,           x2, y-dy2*1.5f, 1.5f*s);

				if(!cheated && score > 0.5*greatLimit)
				{
					if(score > int(highscore))
					{
						newHghscr.draw(0.5f*wndW, y+=2.0f*dy, s, true);
					}
				}
			}
			else
				glColor4ub(255,255,255,128);

			FixedFont->drawString(L"Press Esc/Start to continue",        34.0f, wndH - 34.0f,  0.75f);
			FixedFont->drawString(L"Press End/RB to toggle scores", wndW-34.0f, wndH - 34.0f,  0.75f, align::right);
		}
		else
		{
		//----
		//	draw rating
			if(correctKey)		white.glApply();
			else					glColor4ub(255,255,255,128);
			Ratings.draw(rating, wndW+(512-ratingKerning[rating])/2-256.0f-24.0f, wndH-42.0f, 0.5f);

		//----
		//	draw score
			white.glApply();
			if(score>0 && !chanceTime)
			{
				numbers2.drawNumber(score, wndW-16.0f, -8.0f, 0.5f, align::right, align::top, false, 7);
				glColor4ub(128, 128, 128, 255);
				glBlendFunc(GL_SRC_ALPHA, GL_ONE);																				// set additive blend
				numbers2.drawNumber(score, wndW-16.0f, -8.0f, 0.5f, align::right, align::top, false, 7);		//	draw again
				glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
			}
			else if(buttons[0].time > timeS)
			{
			//----
			//	draw countdown
				numbers2.drawNumber(int(-100.0*(buttons[0].time-timeS)), wndW-16.0f, -8.0f, 0.5f);

			//----
			//	draw map quality
				if(StartOptimization)
				{
					FixedFont->drawString(L"map quality",    wndW-96.0f, wndH-34.0f, 0.75f, align::right);
					FixedFont->drawNumber(10.0*PTT::Quality, wndW-34.0f, wndH-34.0f, 0.75f);
				}
			}
		}

	//----
	//	draw titel
		white.glApply();
		note.draw(8.0f, 8.0f);
		std::wstring title = osu.metadata.title;
		if(inputOver && !hideScore)
		{
			title =  L"(" + osu.metadata.artist + L") " + title;
			title += L" (" + osu.metadata.version;
			if(PTT::BonusMode)
				title += L" - Bonus Mode";
			title += L")";
		}
		FixedFont->drawString(title, borderWidthf, 8.0f);

	//----
		if(paused)
		{
			glColor4ub(0, 0, 0, 180);
			DrawRect(window);
			white.glApply();

			float x1 = 32.0f, x2 = 200.0f, y = 32.0f, dy = 32.0f;
			FixedFont->drawString(L"Title:",		x1, y+=dy);	FixedFont->drawString(osu.metadata.title,		x2, y);
			FixedFont->drawString(L"Artist:",	x1, y+=dy);	FixedFont->drawString(osu.metadata.artist,	x2, y);
			FixedFont->drawString(L"Creator:",	x1, y+=dy);	FixedFont->drawString(osu.metadata.creator,	x2, y);
			FixedFont->drawString(L"Version:",	x1, y+=dy);	FixedFont->drawString(osu.metadata.version,	x2, y);
			FixedFont->drawString(L"Source:",	x1, y+=dy);	FixedFont->drawString(osu.metadata.source,	x2, y);
			FixedFont->drawString(L"Tags:",		x1, y+=dy);	FixedFont->drawString(osu.metadata.tags,		x2, y);

			std::wstring				nfo0  = L"Paused",
											nfo1  = L"Press ",	nfo2  = nfo1,				nfo3;
			if(Input::UsingPad)	{	nfo1 += L"Start";		nfo2 += L"Back";			nfo3 = L"Left thumbstick";		}
			else						{	nfo1 += L"Esc";		nfo2 += L"Backspace";	nfo3 = L"Mousewheel";			}
			nfo1 += L" to continue.";
			nfo2 += L" to return to menu.";
			nfo3 += L" changes volume.";

			FixedFont->drawString(nfo3, 34.0f, wndH - 34.0f,  0.7f);

			glColor4ub(255, 255, 180, 255);
			FixedFont->drawString(nfo0, 0.5f*wndW, 0.4f*wndH, 1.0f, align::centered, align::center);
			FixedFont->drawString(nfo1, 0.5f*wndW, 0.5f*wndH, 1.0f, align::centered, align::center);
			FixedFont->drawString(nfo2, 0.5f*wndW, 0.6f*wndH, 1.0f, align::centered, align::center);

			if(PTT::BonusMode)
			{
//				glColor4f(1.0f, 0.8f, 0.8f, 1.0f);
//				FixedFont->drawString(L"Bonus Mode", 0.5f*wndW, 0.7f*wndH, 1.5f, align::centered, align::center);
				white.glApply();
				bonusMode.draw(0.5f*wndW, 0.7f*wndH, Movable::scale, true);
			}
		}

	//----
	//	was the volume changed (via scrollwheel or thumbstick)
		int newVol = min(100, max(0,int(volume+0.5)));
		if(MasterVolume != newVol && PTT::UseAudio)
		{
			MasterVolume = newVol;
			audio->getMasteringVoice()->SetVolume(0.01f*MasterVolume);
			drawVol = 1.0f;					// message timeout = 1 second
		}

		if(drawVol > 0)
		{
			drawVol -= (float)frameTime;
			glPushMatrix();
			glTranslatef(float(wndW-12), 44.0f+borderWidthf, 0);
			glColor4ub(255, 255, 255, 200);
			texVolume.draw(-116.0f,-36.0f);
			NumbersSmall->drawNumber(MasterVolume);
			glPopMatrix();
		}

	//----
		glFlush();

		SwapBuffers(wnd->getDC());

	}//while loop


//----
//	If we return before we even started, stop optimizing.
	StopOptimizing = true;

//----
//	stop video decoding
	DoVideoDecoding = false;

//----
//	Stop buffer blocking
	if(aFile)
		aFile->setBlockBuffers(false);

//----
//	Tell the input thread to stop updating
	EnterCriticalSection(&updateIsSafeAccess);
	updateIsSafe = false;
	LeaveCriticalSection(&updateIsSafeAccess);

//----
//	put threads into longer sleep phases
	threadsSleep = 20;

//----
//	Save highscore of current song, and save Highscore
	if(!cheated && score > 0.5*greatLimit)
	{
		SetHighscore(score, score > int(greatLimit), ratingCount[5]==0 && ratingCount[4]==0 && ratingCount[3]==0);
		SaveErrors();
	}

}






